from urllib import urlopen
from StringIO import StringIO

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, scoped_session

from ivic.core import Logging
from ivic.core.Utils import GenUUID
from ivic.core.Utils import string_trim
from ivic.vsched.ImportedModel import *
import hashlib

logger = Logging.get_logger('ivic.vsched.Model')
VSCHED_USERID = 0 # FIXME: should use caller's user_id, not scheduler's

def get_Session(connect_string):
    engine = create_engine(connect_string, echo = False)
    return sessionmaker(transactional = False, autoflush = False, bind = engine)

def get_ScopedSession(connect_string):
    #return scoped_session(get_Session(connect_string))
    return get_Session(connect_string)

def get_session(connect_string):
    return get_Session(connect_string)()

MapperClasses = []
def __generic_repr(self):
    try:
        cls = type(self) 
        attrs = cls.__dict__.items()
        is_column = lambda x: x[1] == InstrumentedAttribute
        to_key_value = lambda x: (x[0], getattr(self, x[0]))
        key_val = map(to_key_value, filter(is_column, attrs))
        to_kv_str = lambda x: '%s:%s' % (x[0], string_trim(str(x[1])))
        str_val = ', '.join(map(to_kv_str, key_val))
        return '<%s (%s)>' % (type(self).__name__, str_val)
    except Exception, e:
        logger.error('bad type %s' % type(self))
        return '<%s instance with hash %x >' % (type(self).__name__, hash(self))

def register_generic_repr():
    for attrname, attr in globals().items():
        try:
            if issubclass(attr, Base) and attr != Base:
                MapperClasses.append(attr)
                logger.debug('set %s\'s __repr__ to generic_repr', attrname)
                attr.__repr__ = __generic_repr
        except:
            pass

from xml.dom import minidom
from xml.parsers.expat import ExpatError

class XmlImportError(Exception):
    pass

class XmlReader:
    def get_element_by(self, element, tagname):
        nodes = element.getElementsByTagName(tagname)
        if len(nodes) == 0:
            raise XmlImportError, 'bad format, no tag %s' % tagname
        elif len(nodes) > 1:
            logger.warning('possibly format error, more than one tag %s found', tagname)
        return nodes[0]
        
    def get_string_value(self, element, tagname):
        n = self.get_element_by(element, tagname)
        return n.firstChild.nodeValue.strip()
    
    def get_size_value(self, element, tagname):
        strval = self.get_string_value(element, tagname)
        if not strval or not len(strval):
            raise XmlImportError, 'bad size format, null string'
        
        char = strval[-1].upper()
        if char == 'B':
            unit = 1
        elif char == 'K':
            unit = 1024
        elif char == 'M':
            unit = pow(1024, 2)
        elif char == 'G':
            unit = pow(1024, 3)
        elif char == 'T':
            unit = pow(1024, 4)
        else:
            raise XmlImportError, 'bad size format, not size unit (B/K/M/G/T) in str: %s' % strval
        
        try:
            return int(strval[:-1]) * unit
        except:
            raise XmlImportError, 'bad size format: %s' % strval

    def get_int_value(self, element, tagname):
        strval = self.get_string_value(element, tagname)
        try:
            return int(strval)
        except:
            raise XmlImportError, 'bad format, expected int but got %s' % strval

    def get_child_tagnames(self, element):
        filter_tagnode = lambda x: x.nodeType == x.ELEMENT_NODE
        get_tagname = lambda x: x.tagName
        return map(get_tagname, filter(filter_tagnode, element.childNodes))
    
    def get_capabilities(self, element, captag = 'Capabilities'):
        n = self.get_element_by(element, captag)
        return ','.join(self.get_child_tagnames(n))
    
    def get_xml(self, element, tagname):
        n = self.get_element_by(element, tagname)
        return n.toxml()
    
    def get_multi_xml(self, element, tagname):
        nodes = element.getElementsByTagName(tagname)
        if len(nodes) == 0:
            raise XmlImportError, 'bad format, no tag %s' % tagname
        toxml = lambda x: x.toxml()
        return '\n'.join(map(toxml, nodes))

class XmlImporter(XmlReader):        
    def __import_vnode(self, typ, element):
        vnode = typ()
        vnode.settings = element.toxml()
        vtemp_url = self.get_string_value(element, 'vTemplateRef')
        vnode.vm_temp_id = self.__import_vtemplate_url(vtemp_url)
        self.s.save(vnode)
        logger.debug('saving %s', vnode)
        return vnode
    
    def __import_vtemplate_url(self, url):
        logger.debug('download vTeamplate from %s', url)
        try:
            xml = urlopen(url).read()
            doc = minidom.parseString(xml).documentElement
            xml_type = doc.nodeName
            if xml_type != 'vTemplate':
                raise XmlImportError, 'bad vTemplate format for %s' % url
        except IOError, e:
            logger.exception(e)
            raise XmlImportError, 'error importing referenced vTemplate from %s: %s' % (url, e.message)
        except ExpatError, e:
            logger.exception(e)
            raise XmlImportError, 'bad vTemplate format for %s: %s' % (url, e.message)

        uuid = doc.getAttribute('uuid')        
        try: #find existing boj with same uuid, return its id
            logger.debug('finding vTemplate %s in database', uuid)
            id = self.s.query(VmTemp).filter(VmTemp.uuid == uuid).one().id
            logger.debug('found vTemplate %s in database: id=%d', uuid, id)
            return id
        except: #not found
            logger.debug('not found vTemplate %s in database, try importing', uuid)
        
        obj = VmTemp()
        obj.uuid = uuid
        self.__import_vtemplate_xml(obj, doc)
        self.s.flush()
        return obj.id
    
    def __import_vtemplate_xml(self, vtemp, element):
        vtemp.name = self.get_string_value(element, 'Name')
        vtemp.description = self.get_string_value(element, 'Description')
        vtemp.repository = self.get_string_value(element, 'Repository')
        vtemp.capabilities = self.get_capabilities(element)
    
        os = self.get_element_by(element, 'OS')
        vtemp.os_type = self.get_string_value(os, 'Type')
        vtemp.distribution = self.get_string_value(os, 'Distribution')
        vtemp.kernel = self.get_string_value(os, 'Kernel')
    
        deployinfo = self.get_element_by(element, 'DeployInfo')
        vtemp.deploy_method = self.get_string_value(deployinfo, 'Method')
        vtemp.deploy_url = self.get_string_value(deployinfo, 'URL')
        vtemp.prefer_settings = self.get_xml(deployinfo, 'PreferedSettings')
    
        self.s.save(vtemp)
        logger.debug('saving %s', vtemp)
    
    def __import_vcluster_xml(self, vtemp, element):
        vtemp.user_id = VSCHED_USERID #FIXME: should use caller's user_id, not scheduler's
        vtemp.name = self.get_string_value(element, 'Name')
        vtemp.description = self.get_string_value(element, 'Description')
        vtemp.capabilities = self.get_capabilities(element)
        sharedstorage = self.get_element_by(element, 'SharedStorage')
        vtemp.sharedstorage_size = self.get_size_value(sharedstorage, 'Size')
        vtemp.sharedstorage_local = self.get_string_value(sharedstorage, 'LocalMountDir')

        self.s.save(vtemp)
        logger.debug('saving %s', vtemp)
        self.s.flush() # must flush here, to get template id
    
        headinfo = self.get_element_by(element, 'HeadInfo')
        node = self.get_element_by(headinfo, 'vNode')
        vnode = self.__import_vnode(VclusterTempsVmTemp, node)
        vnode.vcluster_temp_id = vtemp.id
        vnode.count = 1
        vnode.ref_type = 'headnode'

        workinfo = self.get_element_by(element, 'WorkInfo')
        count = self.get_int_value(workinfo, 'Count')
        node = self.get_element_by(workinfo, 'vNode')
        vnode = self.__import_vnode(VclusterTempsVmTemp, node)
        vnode.vcluster_temp_id = vtemp.id
        vnode.count = count
        vnode.ref_type = 'worknode'
        
        vtemp.node_num = count + 1
    
    def __import_vlab_xml(self, vtemp, element):
        vtemp.user_id = VSCHED_USERID
        vtemp.name = self.get_string_value(element, 'Name')
        vtemp.description = self.get_string_value(element, 'Description')
        #FIXME vtemp.capabilities = self.get_capabilities(element)
        vtemp.vswitch_config = self.get_multi_xml(element, 'vSwitch')
        vtemp.xml_content = element.toxml()
        
        self.s.save(vtemp)
        logger.debug('saving %s', vtemp)
        self.s.flush() # must flush here, to get template id
        
        nodes = element.getElementsByTagName('vNode')
        if not nodes:
            raise XmlImportError, 'no vNode in vlab'
        for node in nodes:
            vnode = self.__import_vnode(VlabTempsVmTemp, node)
            vnode.vlab_temp_id = vtemp.id
    
    def __dup_check_create(self, xml_type, uuid):
        if len(uuid) == 0:
            raise XmlImportError, 'no uuid attribute'
        
        tables = {'vLab':VlabTemp, 'vCluster':VclusterTemp, 'vTemplate':VmTemp}
        table = tables[xml_type] #xml_type checked before

        if self.s.query(table).filter(table.uuid == uuid).count() > 0:
            raise XmlImportError, 'record exists with the same uuid(%s)' % uuid
        
        obj = table()
        obj.uuid = uuid
        return obj
    
    def do_import(self, xml, session):
        self.s = session
        try:
            doc = minidom.parseString(xml).documentElement
            xml_type = doc.nodeName
            name = '_XmlImporter__import_%s_xml' % xml_type.lower()
            do_import_xml = getattr(self, name)
        except ExpatError, e:
            raise XmlImportError, 'error in xml: %s' % e.message
        except AttributeError:
            raise XmlImportError, 'bad xml type %s' % xml_type

        uuid = doc.getAttribute('uuid')        
        obj = self.__dup_check_create(xml_type, uuid)
        
        try:
            self.s.begin()
            do_import_xml(obj, doc)
            self.s.commit()
            return '%s@%d' % (xml_type, obj.id)
        except XmlImportError, e:
            self.s.rollback()
            #self.s.close()
            #remove_template(obj, self.s, remove_instances = False)
            raise

class XmlExportError(Exception):
    pass

class XmlWriter:
    def __init__(self, iob):
        self.iob = iob
        self.indent = 0
        
    def print_line(self, line):
        space = '    ' * self.indent
        print >> self.iob, space + line.strip()
    
    def print_xml_version(self):
        self.print_line('<?xml version="1.0"?>')
    
    def print_tag(self, tagname, value):
        if not value:
            self.print_line('<%s/>' % tagname)
        else:
            self.print_line('<%s>%s</%s>' % (tagname, value, tagname))
    
    def print_int_tag(self, tagname, value):
        self.print_tag(tagname, str(value))
    
    def print_size_tag(self, tagname, value):
        units = ['B', 'K', 'M', 'G', 'T']
        unit_idx = 0
        while value % 1024 == 0:
            value /= 1024
            unit_idx += 1
        self.print_tag(tagname, '%d%s' % (value, units[unit_idx]) )

    def print_tag_open(self, tagname, attributes = None):
        if attributes:
            kv_fmt = lambda x: '%s="%s"' % (x[0], x[1])
            strattr = ' ' + ' '.join(map(kv_fmt, attributes.items()))
        else:
            strattr = ''
        self.print_line('<%s%s>' % (tagname, strattr))
        self.indent += 1
        
    def print_tag_close(self, tagname):
        self.indent -= 1
        self.print_line('</%s>' % tagname)
        
    def print_capabilities(self, capabilities):
        if not capabilities:
            return
        caps = capabilities.split(',')
        if len(caps) == 1 and caps[0] == '':
            self.print_line('<Capabilities/>')
            return
        self.print_tag_open('Capabilities')
        for cap in caps:
            self.print_tag(cap, None)
        self.print_tag_close('Capabilities')
    
    def print_dom_tree(self, root):
        filter_node = lambda x: x.nodeType == x.ELEMENT_NODE
        for node in filter(filter_node, root.childNodes):
            if len(node.childNodes) == 1 and node.firstChild.nodeType == node.TEXT_NODE:
                self.print_tag(node.tagName, node.firstChild.nodeValue.strip())
            else:
                self.print_tag_open(node.tagName, node._get_attributes())
                self.print_dom_tree(node)
                self.print_tag_close(node.tagName)
    
    def print_xml(self, xml):
        if not xml:
            return
        xml = '<dummy>%s</dummy>' % xml
        try:
            doc = minidom.parseString(xml)
        except:
            logger.errror('bad xml format: %s', trip(xml))
        self.print_dom_tree(doc.documentElement)
        #for l in xml.splitlines():
        #    self.print_line(l)

class XmlExporter:
    
    def __export_vlab_id(self, id):
        vtemp = self.s.query(VlabTemp).get(id)
        self.__export_vlab_obj(vtemp);

    def __export_vlab_obj(self, vtemp):
        id = vtemp.id
        self.xmlwriter.print_xml_version()
        self.xmlwriter.print_tag_open('vLab')
        self.xmlwriter.print_tag('Id', vtemp.id)
        self.xmlwriter.print_tag('Name', vtemp.name)
        self.xmlwriter.print_tag('Description', vtemp.description)
        self.xmlwriter.print_tag('User_id', vtemp.user_id)
        self.xmlwriter.print_tag('Uuid', vtemp.uuid)
        for vnode in self.s.query(VlabTempsVmTemp).filter(VlabTempsVmTemp.vlab_temp_id == id):
            self.xmlwriter.print_xml(vnode.settings)
        self.xmlwriter.print_xml(vtemp.vswitch_config)
        self.xmlwriter.print_tag_close('vLab')

    def __export_vli_obj(self, vli):
        id = vli.id
        self.xmlwriter.print_xml_version()
        self.xmlwriter.print_tag_open('vli')
        self.xmlwriter.print_tag('Id', vli.id)
        self.xmlwriter.print_tag('Vlab_temp_id', vli.vlab_temp_id)
        self.xmlwriter.print_tag('Created_at', vli.created_at)
        self.xmlwriter.print_tag('User_id', vli.user_id)
        self.xmlwriter.print_tag('Status', vli.status)
        self.xmlwriter.print_tag('Uuid', vli.uuid)
        self.xmlwriter.print_tag('Name', vli.name)
        self.xmlwriter.print_tag('Description', vli.description)
        self.xmlwriter.print_tag('Deployment', vli.deployment)
        self.xmlwriter.print_tag_close('vli')

    def __export_vci_obj(self, vci):
        id = vci.id
        self.xmlwriter.print_xml_version()
        self.xmlwriter.print_tag_open('vci')
        self.xmlwriter.print_tag('Id', vci.id)
        self.xmlwriter.print_tag('Vcluster_temp_id', vci.vcluster_temp_id)
        self.xmlwriter.print_tag('Created_at', vci.created_at)
        self.xmlwriter.print_tag('User_id', vci.user_id)
        self.xmlwriter.print_tag('Status', vci.status)
        self.xmlwriter.print_tag('Uuid', vci.uuid)
        self.xmlwriter.print_tag('Name', vci.name)
        self.xmlwriter.print_tag('Description', vci.description)
        self.xmlwriter.print_tag('Deployment', vci.deployment)
        self.xmlwriter.print_tag('Worknode_count', vci.worknode_count)
        self.xmlwriter.print_tag_close('vci')
        
    def __export_vcluster_id(self, id):
        vtemp = self.s.query(VclusterTemp).get(id)
        self.__export_vcluster_obj(vtemp)

    def __export_vcluster_obj(self, vtemp):
        id = vtemp.id
        self.xmlwriter.print_xml_version()
        self.xmlwriter.print_tag_open('vCluster')
        self.xmlwriter.print_tag('Name', vtemp.name)
        self.xmlwriter.print_tag('Description', vtemp.description)
        self.xmlwriter.print_capabilities(vtemp.capabilities)
        
        self.xmlwriter.print_tag_open('HeadInfo')
        try:
            vnode = self.s.query(VclusterTempsVmTemp).filter(VclusterTempsVmTemp.vcluster_temp_id == id).filter(VclusterTempsVmTemp.ref_type == 'headnode').one()
            self.xmlwriter.print_xml(vnode.settings)
        except:
            logger.error('no head node info')
        self.xmlwriter.print_tag_close('HeadInfo')
        
        self.xmlwriter.print_tag_open('WorkInfo')
        try:
            vnode = self.s.query(VclusterTempsVmTemp).filter(VclusterTempsVmTemp.vcluster_temp_id == id).filter(VclusterTempsVmTemp.ref_type == 'worknode').one()
            self.xmlwriter.print_int_tag('Count', vnode.count)
            self.xmlwriter.print_xml(vnode.settings)
        except:
            logger.error('no work node info')
        self.xmlwriter.print_tag_close('WorkInfo')
        
        self.xmlwriter.print_tag_open('SharedStorage')
        self.xmlwriter.print_size_tag('Size', vtemp.sharedstorage_size)
        self.xmlwriter.print_tag('LocalMountDir', vtemp.sharedstorage_local)
        self.xmlwriter.print_tag_close('SharedStorage')
        
        self.xmlwriter.print_tag_close('vCluster')
    
    def __export_vtemplate_id(self, id):
        vtemp = self.s.query(VmTemp).get(id)
        self.__export_vtemplate_obj(vtemp)

    def __export_vtemplate_obj(self, vtemp):
        id = vtemp.id
        self.xmlwriter.print_xml_version()
        self.xmlwriter.print_tag_open('vTemplate')
        self.xmlwriter.print_tag('Name', vtemp.name)
        self.xmlwriter.print_tag('Description', vtemp.description)
        self.xmlwriter.print_capabilities(vtemp.capabilities)
        self.xmlwriter.print_tag_open('OS')
        self.xmlwriter.print_tag('Type', vtemp.os_type)
        self.xmlwriter.print_tag('Distribution', vtemp.distribution)
        self.xmlwriter.print_tag('Kernel', vtemp.kernel)
        self.xmlwriter.print_tag_close('OS')
        self.xmlwriter.print_tag('Repository', vtemp.repository)
        self.xmlwriter.print_tag_open('DeployInfo')
        self.xmlwriter.print_tag('URL', vtemp.deploy_url)
        self.xmlwriter.print_tag('Method', vtemp.deploy_method)
        self.xmlwriter.print_xml(vtemp.prefer_settings)
        self.xmlwriter.print_tag_close('DeployInfo')
        self.xmlwriter.print_tag_close('vTemplate')

    def generate_vnode(self, vmt):
        iob = StringIO()
        xmlwriter = XmlWriter(iob)
        xmlwriter.print_xml_version()
        xmlwriter.print_tag_open('vNode')
        xmlwriter.print_tag('vTemplateRef', vmt.url)
        xmlwriter.print_tag('Hostname', 'mu')
        xmldoc = minidom.parseString(vmt.prefer_settings)
        mem = xmldoc.getElementsByTagName("Mem")[0].firstChild.data
        disksize = xmldoc.getElementsByTagName("DiskSize")[0].firstChild.data
        xmlwriter.print_tag('Mem', mem)
        xmlwriter.print_tag('DiskSize', disksize)
        xmlwriter.print_tag_close('vNode')
        return iob.getvalue()
    
    def do_export(self, obj, session):
        self.s = session
        iob = StringIO()
        self.xmlwriter = XmlWriter(iob)

        functions = {
            VlabTemp: self.__export_vlab_obj,
            VclusterTemp: self.__export_vcluster_obj,
            VmTemp: self.__export_vtemplate_obj,
            VlabInstance: self.__export_vli_obj,
            VirtualClusterInstance: self.__export_vci_obj,
        }
        
        try:
            do_export_obj = functions[type(obj)]
        except KeyError:
            raise XmlExportError, 'bad object type %s' % type(obj)

        do_export_obj(obj)
        return iob.getvalue()

    def do_export_id(self, key, session):
        self.s = session
        iob = StringIO()
        self.xmlwriter = XmlWriter(iob)

        xml_type, strid = key.split('@')
        try:
            id = int(strid)
        except:
            raise XmlExportError, 'bad id value %s' % strid

        try:
            name = '_XmlExporter__export_%s_id' % xml_type.lower()
            do_export_xml = getattr(self, name)
        except:
            raise XmlExportError, 'bad xml document type %s' % xml_type

        do_export_xml(id)
        return iob.getvalue()

class ObjectRemoverError(Exception):
    pass

class ObjectRemover:
    def __remove(self, obj):
        logger.debug('deleting %s', obj)
        self.s.delete(obj)
        
    def __remove_vnode(self, obj):
        self.__remove(obj)
        
    def __remove_nic(self, obj):
        self.__remove(obj)
        
    def __remove_vswitch(self, obj):
        for i in self.s.query(Nic).filter(Nic.vswitch_id == obj.id):
            self.__remove_nic(i)
        self.__remove(obj)

    def __remove_vm_instance(self, obj):
        self.__remove(obj)
    
    def __remove_vcluster_instance(self, obj):
        for i in self.s.query(VirtualMachineInstance).filter(VirtualMachineInstance.virtual_cluster_instance_id == obj.id):
            self.__remove_vm_instance(i)
        for i in self.s.query(Vswitch).filter(Vswitch.virtual_cluster_instance_id == obj.id):
            self.__remove_vswitch(i)
        self.__remove(obj)

    def __remove_vcluster_temp(self, obj):
        for i in self.s.query(VirtualClusterInstance).filter(VirtualClusterInstance.vcluster_temp_id == obj.id):
            self.__remove_vcluster_instance(i)
        for i in self.s.query(VclusterTempsVmTemp).filter(VclusterTempsVmTemp.vcluster_temp_id == obj.id):
            self.__remove_vnode(i)
        self.__remove(obj)

    def __remove_vlab_instance(self, obj):
        for i in self.s.query(VirtualMachineInstance).filter(VirtualMachineInstance.vlab_instance_id == obj.id):
            self.__remove_vm_instance(i)
        for i in self.s.query(Vswitch).filter(Vswitch.vlab_instance_id == obj.id):
            self.__remove_vswitch(i)
        self.__remove(obj)

    def __remove_vlab_temp(self, obj):
        for i in self.s.query(VlabInstance).filter(VlabInstance.vlab_temp_id == obj.id):
            self.__remove_vlab_instance(i)
        for i in self.s.query(VlabTempsVmTemp).filter(VlabTempsVmTemp.vlab_temp_id == obj.id):
            self.__remove_vnode(i)
        self.__remove(obj)

    def __remove_vm_temp(self, obj):
        if self.remove_instances:
            vlab_set = set()
            vcluster_set = set()
            for i in self.s.query(VirtualMachineInstance).filter(VirtualMachineInstance.vm_temp_id == obj.id):
                vlab_set.add(i.vlab_instance_id)
                vcluster_set.add(i.virtual_cluster_instance_id)
            
            vlab_temp_set = set()
            for vlab_id in filter(None, vlab_set):
                vlab = self.s.query(VlabInstance).get(vlab_id)
                if vlab:
                    vlab_temp_set.add(vlab.vlab_temp_id)
                
            vcluster_temp_set = set()
            for vcluster_id in filter(None, vcluster_set):
                vcluster = self.s.query(VirtualClusterInstance).get(vcluster_id)
                if vcluster:
                    vcluster_temp_set.add(vcluster.vcluster_temp_id)
            
            for vlab_temp_id in filter(None, vlab_temp_set):
                vlabtemp = self.s.query(VlabTemp).get(vlab_temp_id)
                if vlabtemp:
                    self.__remove_vlab_temp(vlabtemp)
            
            for vcluster_temp_id in filter(None, vcluster_temp_set):
                vclustertemp = self.s.query(VclusterTemp).get(vcluster_temp_id)
                if vclustertemp:
                    self.__remove_vcluster_temp(vclustertemp)

            for i in self.s.query(VirtualMachineInstance).filter(VirtualMachineInstance.vm_temp_id == obj.id):
                self.__remove_vm_instance(i)
        self.__remove(obj)

    def do_remove_template(self, obj, session, remove_instances = True):
        if not obj or not obj.id:
            return

        table = type(obj)
        obj = session.query(table).get(obj.id)
        if not obj:
            return

        functions = {
            VlabTemp: self.__remove_vlab_temp,
            VclusterTemp: self.__remove_vcluster_temp,
            VmTemp: self.__remove_vm_temp
        }
        try:
            do_remove_obj = functions[table]
        except KeyError:
            raise ObjectRemoverError, 'bad object type %s' % table

        self.s = session
        self.remove_instances = remove_instances
        do_remove_obj(obj)
        session.flush()

    def do_remove_instance(self, obj, session):
        if not obj or not obj.id:
            return

        table = type(obj)
        obj = session.query(table).get(obj.id)
        if not obj:
            return

        functions = {
            VlabInstance: self.__remove_vlab_instance,
            VirtualClusterInstance: self.__remove_vcluster_instance,
            VirtualMachineInstance: self.__remove_vm_instance,
            Vswitch: self.__remove_vswitch,
            Nic: self.__remove_nic
        }
        try:
            do_remove_obj = functions[table]
        except KeyError:
            raise ObjectRemoverError, 'bad object type %s' % table

        self.s = session
        do_remove_obj(obj)
        session.flush()

class InstantiateError(Exception):
    pass

class InstanceGenerator(XmlReader):
    def __get_string_from_xml(self, tagname, xmlstr):
        doc = minidom.parseString(xmlstr).documentElement
        return self.get_string_value(doc, tagname)
        
    def __get_vswitch_ids(self, xmlstr):
        xmlstr = '<dummy>%s</dummy>' % xmlstr
        doc = minidom.parseString(xmlstr).documentElement
        nodes = doc.getElementsByTagName('vSwitch')
        get_id = lambda x: x.getAttribute('id')
        return map(get_id, nodes)

    def __generate_vm_instance(self, vnode):
        vm = VirtualMachineInstance()
        vm.uuid = GenUUID()
        vm.status = 'invalid'
        vm.hostname = self.__get_string_from_xml('Hostname', vnode.settings)
        vm.vm_temp_id = vnode.vm_temp_id
        self.s.save(vm)
        logger.debug('saving %s', vm)
        return vm  

    def __generate_vswitch(self, config_id = 0):
        vswitch = Vswitch()
        vswitch.uuid = GenUUID()
        vswitch.status = 'invalid'
        vswitch.config_id = config_id
        self.s.save(vswitch)
        logger.debug('saving %s', vswitch)
        return vswitch
    
    def __set_string_value(self, root, tagname, value):
        node = self.get_element_by(root, tagname)
        node.firstChild.nodeValue = value

    def __generate_vcluster_vm_settings(self, vm, vnode, idx, vswitch, password):
        doc = minidom.parseString(vnode.settings).documentElement
        if vnode.ref_type == 'headnode':
          ip_idx = idx + 201 #FIXME no more than 200 nodes in vcluster 
        else:
          ip_idx = idx + 1
        self.__set_string_value(doc, 'Hostname', vm.hostname)
        address = '10.' + str(vswitch.id % 65536 / 256) + '.' + str(vswitch.id % 65536 % 256) + '.' + str(vm.node_index + 100)
        gateway = '10.' + str(vswitch.id % 65536 / 256) + '.' + str(vswitch.id % 65536 % 256) + '.' + '1'
        nic_xml = '<NIC id="0">\n<Address>%s</Address>\n<Netmask>255.255.255.0</Netmask>\n<Gateway>%s</Gateway>\n<vSwitchRef>%s</vSwitchRef>\n</NIC>' % (address, gateway, vswitch.uuid)
        nic_element = minidom.parseString(nic_xml).documentElement
        doc.appendChild(nic_element)
        password_xml = '<Password>%s</Password>'%(password)
        password_element = minidom.parseString(password_xml).documentElement
        doc.appendChild(password_element)
        vm.settings = doc.toxml()

    def __generate_vcluster_dnet(self, vm, dnat_param, vswitch):
        for s in dnat_param:
	    hostname = s.split(';')[0]
	    if hostname != vm.hostname:
	        continue
	    dnet = Dnet()
	    dnet.virtual_machine_instance_id = vm.id
	    dnet.vswitch_id = vswitch.id
	    dnet.internal_ip = '10.' + str(vswitch.id % 65536 / 256) + '.' + str(vswitch.id % 65536 %256) + '.' + str(vm.node_index + 100)
	    dnet.internal_port = int(s.split(';')[1])
	    dnet.protocol = s.split(';')[2]
	    self.s.save(dnet)
	    logger.debug('saving %s', dnet)

    def __generate_nic(self, vnode, vm, vlab_id):
        doc = minidom.parseString(vnode.settings).documentElement
        for node in doc.getElementsByTagName('NIC'):
            nic = Nic()
            nic.virtual_machine_instance_id = vm.id
            nic.address = self.get_string_value(node, 'Address')
            nic.netmask = self.get_string_value(node, 'Netmask')
            nic_refid = self.get_int_value(node, 'vSwitchRef')
            vswitch = self.s.query(Vswitch).filter(Vswitch.vlab_instance_id == vlab_id).filter(Vswitch.config_id == nic_refid).one()
            nic.vswitch_id = vswitch.id 
            self.s.save(nic)
            logger.debug('saving %s', nic)
        
    def __generate_vlab_vm_settings(self, vnode, vm, vlab_id, password):
        doc = minidom.parseString(vnode.settings).documentElement
        for node in doc.getElementsByTagName('NIC'):
            nic = Nic()
            nic.virtual_machine_instance_id = vm.id
            nic.address = self.get_string_value(node, 'Address')
            nic.netmask = self.get_string_value(node, 'Netmask')
            nic_refid = self.get_int_value(node, 'vSwitchRef')
            vswitch = self.s.query(Vswitch).filter(Vswitch.vlab_instance_id == vlab_id).filter(Vswitch.config_id == nic_refid).one()
            nic.vswitch_id = vswitch.id 
            self.s.save(nic)
            logger.debug('saving %s', nic)

            elem = self.get_element_by(node, 'vSwitchRef')
            elem.firstChild.nodeValue = vswitch.uuid # replace id with vswitch's uuid

	    for dnat_doc in node.getElementByTagName('dnat'):
	        dnet = Dnet()
		dnet.virtual_machine_instance_id = vm.id
		dnet.vswitch_id = vswitch.id
		dnet.internal_ip = nic.address
		dnet.internal_port = self.get_int_value(dnet, 'internal_port')
		dnet.protocol = self.get_string_value(dnet, 'protocol')
		self.s.save(dnet)
		logger.debug('saving %s', dnet)
        password_xml = '<Password>%s</Password>'%(password)
        password_element = minidom.parseString(password_xml).documentElement
        doc.appendChild(password_element)
        vm.settings = doc.toxml()
        logger.debug('generate new vnode xml: %s', vm.settings)
 
    def __generate_vlab_instance(self, vlab, vtemp, param):
        param_dict = eval(param)
        vlab.uuid = GenUUID()
        vlab.vlab_temp_id = vtemp.id
        vlab.name = '#%s#' % vtemp.name
        vlab.description = vtemp.description
        vlab.user_id = VSCHED_USERID
        if param_dict.has_key('user_id'):
            vlab.user_id = int(param_dict['user_id'])
        if param_dict.has_key('password'):
            password = hashlib.sha1(param_dict['password']).hexdigest()
        vlab.status = 'invalid'
        self.s.save(vlab)
        logger.debug('saving %s', vlab)
        self.s.flush() # generate vlab.id
        
	doc = minidom.parseString(vtemp.vswitch_config)
	for vSwitch in doc.getElementsByTagName("vSwitch"):
	    config_id = int(vSwitch.getAttribute("id"))
	    vswitch = self.__generate_vswitch(config_id)
	    vswitch.ip = vSwitch.getElementsByTagName("ip")[0].firstChild.data
	    vswitch.netmask = vSwitch.getElementsByTagName("netmask")[0].firstChild.data
            vswitch.connect_type = 'openvpn'
	    if vSwitch.getElementsByTafName("internet_access")[0].firstChild.data == "true":
	        vswitch.internet_access = True
	    else:
	        vswitch.internet_access = False
	    self.s.flush()

        for vnode in self.s.query(VlabTempsVmTemp).filter(VlabTempsVmTemp.vlab_temp_id == vtemp.id):
            vm = self.__generate_vm_instance(vnode)
            vm.vlab_instance_id = vlab.id
            self.s.flush() # generate vm.id
            self.__generate_vlab_vm_settings(vnode, vm, vlab.id, password)
        return vlab

    def __clone_vswitch(self, s_vswitch):
        vswitch = Vswitch()
        vswitch.uuid = GenUUID()
        vswitch.status = 'invalid'
        vswitch.config_id = s_vswitch.config_id
        vswitch.internet_access = s_vswitch.internet_access
        vswitch.ip = s_vswitch.ip
        vswitch.netmask = s_vswitch.netmask
	vswitch.connect_type = s_vswitch.connect_type
        self.s.save(vswitch)
        logger.debug('saving %s', vswitch)
        return vswitch	

    def __clone_vmi(self, s_vmi):
	vm = VirtualMachineInstance()
	vm.uuid = GenUUID()
	vm.status = 'invalid'
	vm.hostname = s_vmi.hostname
	vm.vm_temp_id = s_vmi.vm_temp_id
	self.s.save(vm)
	logger.debug('saving %s', vm)
	return vm

    def __clone_nic(self, s_nic):
	nic = Nic()
	nic.address = s_nic.address
	nic.netmask = s_nic.netmask
	self.s.save(nic)
	logger.debug('saving %s', nic)
	return nic

    def __clone_dnet(self, s_dnet):
        dnet = Dnet()
        dnet.internal_ip = s_dnet.internal_ip
        dnet.internal_port = s_dnet.internal_port
        dnet.protocol = s_dnet.protocol
        self.s.save(dnet)
        logger.debug('saving %s', dnet)
        return dnet

    def __clone_vcluster_instance(self, n_vcluster, s_vcluster, user_id, job_id):
	n_vcluster.uuid = GenUUID()
	n_vcluster.vcluster_temp_id = s_vcluster.vcluster_temp_id;
	n_vcluster.name = s_vcluster.name
	n_vcluster.description = s_vcluster.description
	n_vcluster.worknode_count = s_vcluster.worknode_count
	n_vcluster.user_id = user_id
	n_vcluster.job_id = job_id
	n_vcluster.status = 'invalid'
	self.s.save(n_vcluster)
	logger.debug('saving %s', n_vcluster)
	self.s.flush()

	s_vswitch = self.s.query(Vswitch).filter(Vswitch.virtual_cluster_instance_id == s_vcluster.id)[0]
	vswitch = self.__clone_vswitch(s_vswitch)
	vswitch.virtual_cluster_instance_id = n_vcluster.id
	self.s.flush()
	
	vmi_dict = {}
	for s_vmi in self.s.query(VirtualMachineInstance).filter(VirtualMachineInstance.virtual_cluster_instance_id == s_vcluster.id):
	    vmi = self.__clone_vmi(s_vmi)
	    vmi.virtual_cluster_instance_id = n_vcluster.id
	    vmi.node_index = s_vmi.node_index
	    doc = minidom.parseString(s_vmi.settings).documentElement
	    doc.getElementsByTagName("vSwitchRef")[0].firstChild.data = vswitch.uuid
	    vmi.settings = doc.toxml()
	    self.s.flush()
	    vmi_dict[vmi.uuid] = s_vmi.uuid
            for s_dnet in self.s.query(Dnet).filter(Dnet.virtual_machine_instance_id == s_vmi.id):
                dnet = self.__clone_dnet(s_dnet)
                dnet.virtual_machine_instance_id = vmi.id
                dnet.vswitch_id = vswitch.id
                self.s.flush()
	
	return n_vcluster, vmi_dict

    def __clone_vlab_instance(self, n_vlab, s_vlab, user_id, job_id):
	n_vlab.uuid = GenUUID()
	n_vlab.vlab_temp_id = s_vlab.vlab_temp_id;
	n_vlab.name = s_vlab.name
	n_vlab.description = s_vlab.description
	n_vlab.job_id = job_id
	n_vlab.user_id = user_id
	n_vlab.status = 'invalid'
	self.s.save(n_vlab)
	logger.debug('saving %s', n_vlab)
	self.s.flush()
	 
	vswitch_dict = {}
	for s_vswitch in self.s.query(Vswitch).filter(Vswitch.vlab_instance_id == s_vlab.id):
	    vswitch = self.__clone_vswitch(s_vswitch)
	    vswitch.vlab_instance_id = n_vlab.id
	    vswitch_dict[s_vswitch.uuid] = vswitch.uuid
	    self.s.flush()
	     
	vmi_dict = {}
	for s_vmi in self.s.query(VirtualMachineInstance).filter(VirtualMachineInstance.vlab_instance_id == s_vlab.id):
	    vmi = self.__clone_vmi(s_vmi)
	    vmi.vlab_instance_id = n_vlab.id
	    doc = minidom.parseString(s_vmi.settings).documentElement
	    self.s.flush()
	    for vSwitchRef in doc.getElementsByTagName("vSwitchRef"):
	        vSwitchRef.firstChild.data = vswitch_dict[vSwitchRef.firstChild.data]
	    vmi.settings = doc.toxml()
	    for s_nic in self.s.query(Nic).filter(Nic.virtual_machine_instance_id == s_vmi.id):
		nic = self.__clone_nic(s_nic)
		nic.virtual_machine_instance_id = vmi.id
		s_vswitch = self.s.query(Vswitch).get(s_nic.vswitch_id)
		n_vswitch = self.s.query(Vswitch).filter(Vswitch.uuid == vswitch_dict[s_vswitch.uuid])[0]
		nic.vswitch_id = n_vswitch.id
		self.s.flush()
	    vmi_dict[vmi.uuid] = s_vmi.uuid
            for s_dnet in self.s.query(Dnet).filter(Dnet.virtual_machine_instance_id == s_vmi.id):
                dnet = self.__clone_dnet(s_dnet)
                dnet.virtual_machine_instance_id = vmi.id
                s_vswitch = self.s.query(Vswitch).get(s_dnet.vswitch_id)
                n_vswitch = self.s.query(Vswitch).filter(Vswitch.uuid == vswitch_dict[s_vswitch.uuid])[0]
                dnet.vswitch_id = n_vswitch.id
                self.s.flush()

	return n_vlab, vmi_dict
   
    def __generate_vcluster_instance(self, vcluster, vtemp, param):
        param_dict = eval(param)
        vcluster.uuid = GenUUID()
        vcluster.vcluster_temp_id = vtemp.id
        vcluster.name = '#%s#' % vtemp.name
        vcluster.description = vtemp.description
        vcluster.user_id = VSCHED_USERID
        if param_dict.has_key('user_id'):
            vcluster.user_id = int(param_dict['user_id'])
        vcluster.status = 'invalid'
        if param_dict.has_key('password'):
            password = hashlib.sha1(param_dict['password']).hexdigest()
        self.s.save(vcluster)
        logger.debug('saving %s', vcluster)
        self.s.flush() # generate vcluster.id

        vswitch = self.__generate_vswitch()
        self.s.flush()
        vswitch.virtual_cluster_instance_id = vcluster.id
        vswitch.internet_access = True
        vswitch.ip = '10.' + str(vswitch.id % 65536 / 256) + '.' + str(vswitch.id % 65536 % 256) + '.1'
        vswitch.netmask = '255.255.255.0' 
        vswitch.connect_type = 'openvpn'

	dnat_param = []
	if param_dict.has_key('dnat'):
	    dnat_param = param_dict['dnat']

        for vnode in self.s.query(VclusterTempsVmTemp).filter(VclusterTempsVmTemp.vcluster_temp_id == vtemp.id):
            if vnode.ref_type == 'headnode':
                vm = self.__generate_vm_instance(vnode)
                vm.hostname = 'mu'
                vm.node_index = 0
                vm.virtual_cluster_instance_id = vcluster.id
                self.__generate_vcluster_vm_settings(vm, vnode, 0, vswitch, password)
	        self.s.flush()
	        self.__generate_vcluster_dnet(vm, dnat_param, vswitch)
            else:
                if param_dict.has_key('worknode_count'):
                    count = param_dict['worknode_count']
                else:
                    count = vnode.count;

                for i in range(count):
                    vm = self.__generate_vm_instance(vnode)
                    vm.hostname += '%d' % (i + 1) #cluster hostname start from 1
                    vm.node_index = i + 1
                    vm.virtual_cluster_instance_id = vcluster.id
                    self.__generate_vcluster_vm_settings(vm, vnode, i, vswitch, password)
		    self.s.flush()
		    self.__generate_vcluster_dnet(vm, dnat_param, vswitch)
	self.s.flush()
                       
        return vcluster
        
    def do_generate(self, vtemp, session, param):
        self.s = session
        try:
            functions = {VlabTemp:self.__generate_vlab_instance, VclusterTemp:self.__generate_vcluster_instance}
            mapper = type(vtemp)
            my_gen = functions[mapper]
            instanceType = {VlabTemp:VlabInstance, VclusterTemp:VirtualClusterInstance}
            instance = instanceType[mapper]()
        except KeyError:
            raise InstantiateError, 'bad template type: %s' % type(vtemp)

        try:
            self.s.begin()
            my_gen(instance, vtemp, param)
            self.s.commit()
            return instance
        except XmlImportError, e:
            self.s.rollback()
            #self.s.close()
            #remove_instance(instance, self.s)
            raise InstantiateError, 'generate instance error: %s' % e.message

    def do_clone(self, obj, session, user_id, job_id):
        self.s = session
	try:
	    functions = {VirtualClusterInstance:self.__clone_vcluster_instance, VlabInstance:self.__clone_vlab_instance}
	    mapper = type(obj)
	    my_clone = functions[mapper]
	    instance = mapper()
	except KeyError:
	    raise InstantiateError, 'bad instance type: %s' % type(obj)
	 
	try:
	    self.s.begin()
	    instance, vmi_dict = my_clone(instance, obj, user_id, job_id)
	    self.s.commit()
	    return instance, vmi_dict
	except XmlImportError, e:
	    self.s.roollback()
	    raise InstantiateError, 'clone instance error: %s' % e.message

import_xml = XmlImporter().do_import
export_xml_id = XmlExporter().do_export_id
export_xml = XmlExporter().do_export
remove_template = ObjectRemover().do_remove_template
remove_instance = ObjectRemover().do_remove_instance
generate_instance = InstanceGenerator().do_generate
generate_vnode = XmlExporter().generate_vnode
clone_instance = InstanceGenerator().do_clone
